package ip_app

import (
	"context"
	"encoding/json"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis/v8"
)

var (
	LastChecked        int64 = 0
	IsUnderMaintenance       = false
)

const FilePath = "../Chapter05/GeoLite2-City-CSV_20200121/"

type CityInfo struct {
	CityId  string
	Country string
	Region  string
	City    string
}

type IpService struct {
	conn *redis.Client
	ctx  context.Context
}

func (c *IpService) IpToScore(ip string) int64 {
	var score int64 = 0
	for _, v := range strings.Split(ip, ".") {
		n, _ := strconv.ParseInt(v, 10, 0)
		score = score*256 + n
	}
	return score
}

func (c *IpService) ImportIpsToRedis(filename string) {
	res := CSVReader(filename)
	pipe := c.conn.Pipeline()
	for count, row := range res {
		var (
			startIp string
			resIP   int64
		)
		if len(row) == 0 {
			startIp = ""
		} else {
			startIp = row[0]
		}
		if strings.Contains(strings.ToLower(startIp), "i") {
			continue
		}
		if strings.Contains(startIp, ".") {
			resIP = c.IpToScore(startIp)
		} else {
			var err error
			resIP, err = strconv.ParseInt(startIp, 10, 64)
			if err != nil {
				continue
			}
		}
		cityID := row[2] + "_" + strconv.Itoa(count)
		pipe.ZAdd(c.ctx, "ip2cityid:", &redis.Z{Member: cityID, Score: float64(resIP)})
		if (count+1)%1000 == 0 {
			if _, err := pipe.Exec(c.ctx); err != nil {
				log.Println("pipeline err in ImportIpsToRedis: ", err)
				return
			}
		}
	}

	if _, err := pipe.Exec(c.ctx); err != nil {
		log.Println("pipeline err in ImportIpsToRedis: ", err)
		return
	}
}

func (c *IpService) ImportCityToRedis(filename string) {
	res := CSVReader(filename)
	pipe := c.conn.Pipeline()
	for count, row := range res {
		if len(row) < 4 || !IsDigital(row[0]) {
			continue
		}

		city := CityInfo{
			CityId:  row[0],
			Country: row[1],
			Region:  row[2],
			City:    row[3],
		}

		value, err := json.Marshal(city)
		if err != nil {
			log.Println("marshal json failed, err: ", err)
		}
		pipe.HSet(c.ctx, "cityid2city:", city.CityId, value)
		if (count+1)%1000 == 0 {
			if _, err := pipe.Exec(c.ctx); err != nil {
				log.Println("pipeline err in ImportCityToRedis: ", err)
				return
			}
		}
	}

	if _, err := pipe.Exec(c.ctx); err != nil {
		log.Println("pipeline err in ImportCityToRedis: ", err)
		return
	}
}

func (c *IpService) FindCityByIp(ip string) string {
	ipAddress := strconv.Itoa(int(c.IpToScore(ip)))
	res := c.conn.ZRevRangeByScore(c.ctx, "ip2cityid:", &redis.ZRangeBy{Max: ipAddress, Min: "0", Offset: 0, Count: 1}).Val()
	if len(res) == 0 {
		return ""
	}
	cityId := strings.Split(res[0], "_")[0]
	var result CityInfo
	if err := json.Unmarshal([]byte(c.conn.HGet(c.ctx, "cityid2city:", cityId).Val()), &result); err != nil {
		log.Fatalln("unmarshal err: ", err)
	}
	return strings.Join([]string{result.CityId, result.City, result.Country, result.Region}, " ")
}

func (c *IpService) IsUnderMaintenance() bool {
	if LastChecked < time.Now().Unix()-1 {
		LastChecked = time.Now().Unix()
		IsUnderMaintenance = c.conn.Get(c.ctx, "is-under-maintenance").Val() == "yes"
	}
	return IsUnderMaintenance
}
